/*-
 * Copyright (C) 2018-2020, Shanghai Trusted Industry Control Platform Co., Ltd.
 * Created by 周正辉 (Zhenghui Zhou) <zhouzhenghui@ticpsh.com>, 赵楷文 (Kaiwen
 * Zhao) <zhaokaiwen@ticpsh.com>
 */

#include "libtestif.h"

#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Tooling/Tooling.h>
#include <llvm/Support/Path.h>

#include <map>

#include "dependency_graph.h"
#include "testif.h"

using namespace llvm;
using namespace clang;
using namespace clang::tooling;
using njson = nlohmann::json;

namespace rocket {
namespace testif {

class MyFrontendActionFactory : public FrontendActionFactory {
 protected:
  std::vector<std::string> files;
  std::map<std::string, std::string> relative_paths;
  const share::RocketAnalyzeSetting &setting;

 public:
  bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
                     FileManager *Files,
                     std::shared_ptr<PCHContainerOperations> PCHContainerOps,
                     DiagnosticConsumer *DiagConsumer) {
    llvm::SmallString<256> path;
    llvm::sys::fs::current_path(path);
    Invocation->getFileSystemOpts().WorkingDir = path.c_str();
    for (auto &file : files) {
      // See clang::FileManager::fillRealPathName for the name of
      // clang::FileEntry
      llvm::SmallString<256> path(file.c_str());
      Files->makeAbsolutePath(path);
      llvm::sys::path::remove_dots(path, /*remove_dot_dot=*/true);
      relative_paths[path.c_str()] = file;
    }
    return FrontendActionFactory::runInvocation(Invocation, Files,
                                                PCHContainerOps, DiagConsumer);
  }

  MyFrontendActionFactory(const std::vector<std::string> &files,
                          const share::RocketAnalyzeSetting &setting)
      : files(files), setting(setting), file_ifs(njson::array()) {}

  njson file_ifs;
};

class TestIfFrontendActionFactory : public MyFrontendActionFactory {
 public:
  // json file_ifs;
  TestIfConsumer *ifconsumer = nullptr;

 public:
  TestIfFrontendActionFactory(const std::vector<std::string> &files,
                              const share::RocketAnalyzeSetting &setting)
      : MyFrontendActionFactory(files, setting) {}

#if LLVM_VERSION_MAJOR < 9
  FrontendAction *create() override { return new TestIfFrontendAction(this); }
#else
  std::unique_ptr<FrontendAction> create() override {
    return std::make_unique<TestIfFrontendAction>(this);
  }
#endif

  /**
   * @brief The TestIfFrontendAction class
   */
  class TestIfFrontendAction : public ASTFrontendAction {
   private:
    TestIfFrontendActionFactory *action_factory;
    njson &file_ifs; /**< if报文*/

   public:
    TestIfFrontendAction(TestIfFrontendActionFactory *action_factory_)
        : action_factory(action_factory_),
          file_ifs(action_factory_->file_ifs) {}

    std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                   StringRef file) override {
      auto file_if_it = file_ifs.insert(file_ifs.end(), njson::object());
      njson &file_if = *file_if_it;
      file_if["file"] =
          std::move(action_factory->relative_paths[(std::string)file]);
      file_if["types"] = njson::object();
      file_if["literals"] = njson::object();
      file_if["globals"] = njson::object();
      file_if["functions"] = njson::array();
      file_if["fixedAddrs"] = njson::object();
      //      file_if["functiondecls"] = njson::object();
      file_if["macros"] = njson::object();
      file_if["lineStatistic"] = njson::object();

      njson &file_info = file_if["environment"] = njson::object();
      if (CI.getLangOpts().CPlusPlus) {
        file_info["@language"] = "c++";
        if (CI.getLangOpts().CPlusPlus11)
          file_info["@standard"] = "11";
        else if (CI.getLangOpts().CPlusPlus14)
          file_info["@standard"] = "14";
        else if (CI.getLangOpts().CPlusPlus17)
          file_info["@standard"] = "17";
#if LLVM_VERSION_MAJOR > 9
        else if (CI.getLangOpts().CPlusPlus20)
          file_info["@standard"] = "20";
#endif
        else
          file_info["@standard"] = "default";
      } else {
        file_info["@language"] = "c";
        if (CI.getLangOpts().C99)
          file_info["@standard"] = "99";
        else if (CI.getLangOpts().C11)
          file_info["@standard"] = "11";
        else if (CI.getLangOpts().C17)
          file_info["@standard"] = "17";
#if LLVM_VERSION_MAJOR < 10
#elif LLVM_VERSION_MAJOR < 18
        else if (CI.getLangOpts().C2x)
          file_info["@standard"] = "2x";
#else
        else if (CI.getLangOpts().C23)
          file_info["@standard"] = "23";
#endif
        else
          file_info["@standard"] = "default";
      }

      // Disable the effects of '-Werror' when
      // using the AnalysisConsumer.
      CI.getPreprocessor().getDiagnostics().setWarningsAsErrors(false);

      action_factory->ifconsumer = new TestIfConsumer(
          action_factory->setting, file_if["types"], file_if["literals"],
          file_if["globals"], file_if["functions"], file_if["fixedAddrs"],
          /*file_if["functiondecls"],*/ file_if["macros"],
          file_if["lineStatistic"]);

      return nullptr;
    }
  };
};

/**
 * @brief 文件依赖关系报文生成
 */
class DependencyFrontendActionFactory : public MyFrontendActionFactory {
  //  std::map<std::string, std::string> relative_paths;
  class DenpendencyFrontendAction : public PreprocessOnlyAction {
   public:
    DenpendencyFrontendAction(DependencyFrontendActionFactory *action_factory_)
        : action_factory(action_factory_) {}
    ~DenpendencyFrontendAction() {
      action_factory->file_ifs.push_back(std::move(file_if));
    }

   protected:
    virtual bool BeginSourceFileAction(CompilerInstance &CI) override {
      const std::string &name =
          action_factory->relative_paths[(std::string)getCurrentFile()];
      file_if["file"] = name;
      file_if["includes"] = njson::object();
      Preprocessor &pp = CI.getPreprocessor();
      pp.SetSuppressIncludeNotFoundError(true);
#if LLVM_VERSION_MAJOR < 9
      pp.addPPCallbacks(llvm::make_unique<DependencyGraph>(
          file_if["includes"], pp, name, CI.getHeaderSearchOpts().Sysroot));
#else
      pp.addPPCallbacks(std::make_unique<DependencyGraph>(
          file_if["includes"], pp, name, CI.getHeaderSearchOpts().Sysroot));
#endif
      return true;
    }

   private:
    DependencyFrontendActionFactory *action_factory;
    njson file_if;
  };

 public:
  njson file_ifs;
  DependencyFrontendActionFactory(const std::vector<std::string> &files,
                                  const share::RocketAnalyzeSetting &setting)
      : MyFrontendActionFactory(njson::array(), setting) {
    //    for (auto &file : files) {
    //      relative_paths[boost::filesystem::absolute(file).string()] = file;
    //    }
  }

#if LLVM_VERSION_MAJOR < 9
  FrontendAction *create() override {
    return new DenpendencyFrontendAction(this);
  }
#else
  std::unique_ptr<FrontendAction> create() override {
    return std::make_unique<DenpendencyFrontendAction>(this);
  }
#endif
};
/**********************************************************/

class TestIfTempFactory {
 public:
  ASTConsumer *consumer;

  std::unique_ptr<clang::ASTConsumer> newASTConsumer() {
    return std::unique_ptr<ASTConsumer>(consumer);
  }
  TestIfTempFactory(TestIfConsumer *consumer) : consumer(consumer) {}
};

njson parse_test_interfaces(const CompilationDatabase &compilations,
                            std::vector<std::string> files,
                            const share::RocketAnalyzeSetting &setting) {
  TestIfFrontendActionFactory action_factory(files, setting);
  ClangTool Tool(compilations, files);
#ifdef CLANG_RUNTIME_DIR
  Tool.appendArgumentsAdjuster(
      tooling::getInsertArgumentAdjuster("-I" CLANG_RUNTIME_DIR "/include"));
#endif
  if (setting.analyze_line_statistic) {
    Tool.appendArgumentsAdjuster(
        tooling::getInsertArgumentAdjuster("-fparse-all-comments"));
  }
  Tool.run(&action_factory);
  TestIfTempFactory tmp_factory(action_factory.ifconsumer);
  Tool.run(
      newFrontendActionFactory(&tmp_factory, action_factory.ifconsumer).get());
  return action_factory.file_ifs;
}

nlohmann::json parse_dependencies(const CompilationDatabase &compilations,
                                  std::vector<std::string> files,
                                  const share::RocketAnalyzeSetting &setting) {
  DependencyFrontendActionFactory action_factory(files, setting);
  ClangTool tool(compilations, files);
#ifdef CLANG_RUNTIME_DIR
  tool.appendArgumentsAdjuster(
      tooling::getInsertArgumentAdjuster("-I" CLANG_RUNTIME_DIR "/include"));
#endif
  if (setting.analyze_line_statistic) {
    tool.appendArgumentsAdjuster(
        tooling::getInsertArgumentAdjuster("-fparse-all-comments"));
  }
  tool.run(&action_factory);
  return action_factory.file_ifs;
}

}  // namespace testif
}  // namespace rocket
